﻿namespace PugLib
{
	using System;
	using System.Collections.Concurrent;
	using System.Diagnostics;
	using System.Threading;
	using System.Threading.Tasks;

	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="TState"></typeparam>
	public sealed class QueuedActions<TState> : IDisposable
	{
		private readonly CancellationTokenSource _cts = new CancellationTokenSource();
		private readonly BlockingCollection<Tuple<Action<TState>, TState>> _queue;
		private Int64 _processedCount;
		private Int32 _startedProcess;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="boundedCapacity"></param>
		public QueuedActions(Int32 boundedCapacity)
		{
			_queue = new BlockingCollection<Tuple<Action<TState>, TState>>(boundedCapacity);
		}

		/// <summary>
		/// 
		/// </summary>
		public QueuedActions()
		{
			_queue = new BlockingCollection<Tuple<Action<TState>, TState>>();
		}

		#region Public Interface

		/// <summary>
		/// The number of processed items through this instance
		/// </summary>
		public Int64 ProcessedCount
		{
			get { return _processedCount; }
		}

		/// <summary>
		/// 
		/// </summary>
		public void Dispose()
		{
			_queue.CompleteAdding();

			_cts.Cancel();

			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="action"></param>
		/// <param name="item"></param>
		public void Enqueue(Action<TState> action, TState item)
		{
			_queue.Add(Tuple.Create(action, item));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <exception cref="System.InvalidOperationException">Process may only be called once per instance</exception>
		public void Process(CountdownEvent completion)
		{
			if (completion == null)
			{
				throw new ArgumentNullException("completion");
			}

			SetProcessStarted();

			Task.Factory.StartNew(() => ProcessInternal(completion), _cts.Token);

			completion.Wait();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <exception cref="System.InvalidOperationException">Process may only be called once per instance</exception>
		public void Process()
		{
			SetProcessStarted();
			Task.Factory.StartNew(ProcessInternal, _cts.Token);
		}

		#endregion Public Interface

		#region Private Interface

		private void SetProcessStarted()
		{
			if (Thread.VolatileRead(ref this._startedProcess) == 1)
			{
				throw new InvalidOperationException("Cannot call Process twice on an ActionQueue");
			}
			Thread.VolatileWrite(ref this._startedProcess, 1);
		}

		private void ProcessInternal(CountdownEvent completion)
		{
			try
			{
				foreach (Tuple<Action<TState>, TState> value in _queue.GetConsumingEnumerable())
				{
					_cts.Token.ThrowIfCancellationRequested();
					value.Item1(value.Item2);
					++_processedCount;
					completion.Signal();
				}
			}
			catch (OperationCanceledException)
			{
				TraceCancel();
				completion.Signal(completion.CurrentCount);
			}
			catch (ObjectDisposedException)
			{
				TraceCancel();
				completion.Signal(completion.CurrentCount);
			}
		}

		private void ProcessInternal()
		{
			try
			{
				foreach (Tuple<Action<TState>, TState> value in _queue.GetConsumingEnumerable())
				{
					_cts.Token.ThrowIfCancellationRequested();
					value.Item1(value.Item2);
					++_processedCount;
				}
			}
			catch (OperationCanceledException)
			{
				TraceCancel();
			}
			catch (ObjectDisposedException)
			{
				TraceCancel();
			}
		}

		private static void TraceCancel()
		{
			Trace.TraceInformation("QueuedActions.Process", "Action canceled");
		}

		#endregion Private Interface
	}
}